package java

import (
	"encoding/base64"
	"fmt"
	. "org.xx/kris2/common"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var (
	LOCAL_TIME_ZONE      *time.Location
	MULTIPLE_DOT_PATTERN *regexp.Regexp
	currentIdentSize     int
)

func init() {
	var err error
	LOCAL_TIME_ZONE, err = time.LoadLocation("Local")
	if err != nil {
		panic(fmt.Sprintf("Can not load local time zone, %s", err))
	}

	MULTIPLE_DOT_PATTERN = regexp.MustCompile(`\.{2,}`)
}

// 获取完整名称。
func getFullName(p1 string, p ...string) (result string) {
	p2 := ""
	if len(p) != 0 {
		p = PackStrings(p)
		if len(p) != 0 {
			p2 = strings.Join(p, ".")
		}
	}

	if p1 == "" && p2 == "" {
		result = ""
	} else if p1 == "" && p2 != "" {
		result = p2
	} else if p1 != "" && p2 == "" {
		result = p1
	} else {
		result = p1 + "." + p2
	}

	if len(result) > 0 {
		result = MULTIPLE_DOT_PATTERN.ReplaceAllString(result, ".")
	}

	return
}

// formatFullName 获取指定类型的完整名称。最后一个点号之前的部分看作包名，之后的部分看作类型名。按照不同的转化规则转化再拼接。
func formatFullName(fullName string) string {
	if fullName == "" {
		panic("Argument \"fullName\" must not be empty")
	} else if fullName == "." {
		panic("Argument \"fullName\" must not be single dot")
	}

	var p = strings.LastIndex(fullName, ".")
	if p > 0 {
		if p == len(fullName)-1 {
			return IdentToC(fullName[:p])
		} else {
			return IdentToC(fullName[:p]) + "." + IdentToPascal(fullName[p+1:])
		}
	} else if p == 0 {
		return IdentToPascal(fullName[1:])
	} else {
		return IdentToPascal(fullName)
	}
}

// 根据java包名生成对应的目录。
func makePackageDir(destination, package_ string) string {
	dir := filepath.Join(destination, strings.Replace(package_, ".", "/", -1))

	err := os.MkdirAll(dir, os.ModeDir|os.ModePerm)
	if err != nil {
		panic(fmt.Sprintf("Can not make directory: %q, %s", dir, err))
	}

	return dir
}

// 对源文件位置进行转义，用于生成注释。
func formatLineInfoComment(lineInfo *LineInfo) string {
	if lineInfo == nil {
		return ""
	} else {
		return fmt.Sprintf("Source: <u>%s</u>", strings.Replace(lineInfo.String(), "\\", "/", -1))
	}
}

// 对描述信息进行转义，用于生成注释。
func formatDescritpionComment(name, description string) string {
	if description == "" {
		return fmt.Sprintf("TODO: Complete descritpion for %s", name)
	} else {
		return fmt.Sprintf("%s", description)
	}
}

func formatFieldType(type_ DbFieldType, enumType string, lineInfo *LineInfo) string {
	switch type_ {
	case FtString:
		return "String"
	case FtInteger:
		return "Long"
	case FtDecimal:
		return "java.math.BigDecimal"
	case FtDouble:
		return "Double"
	case FtDateTime:
		return "java.util.Date"
	case FtBoolean:
		return "Boolean"
	case FtBinary:
		return "byte[]"
	case FtEnum:
		if enumType == "" {
			panic(fmt.Sprintf("EnumType must not be blank (%s)", lineInfo))
		}
		return formatFullName(enumType)
	default:
		panic(fmt.Sprintf("Unknown field type: %s (%s)", type_, lineInfo))
	}
}

func formatResultType(type_ DbResultType, rootPackage string, resultEntity *DbEntity) string {
	switch type_ {
	case RtEntities:
		return "java.util.List<" + formatFullName(getFullName(rootPackage, resultEntity.Package, "domain", resultEntity.Name)) + ">"
	case RtEntity:
		return formatFullName(getFullName(rootPackage, resultEntity.Package, "domain", resultEntity.Name))
	case RtString:
		return "String"
	case RtInteger:
		return "Long"
	case RtDecimal:
		return "java.math.BigDecimal"
	case RtDouble:
		return "Double"
	case RtDateTime:
		return "java.util.Date"
	case RtBoolean:
		return "Boolean"
	case RtBinary:
		return "byte[]"
	case RtObjects:
		return "Object[]"
	case RtStringList:
		return "java.util.List<String>"
	case RtIntegerList:
		return "java.util.List<Long>"
	case RtDecimalList:
		return "java.util.List<java.util.BigDecimal>"
	case RtDoubleList:
		return "java.util.List<Double>"
	case RtDateTimeList:
		return "java.util.List<java.util.Date>"
	case RtBooleanList:
		return "java.util.List<Boolean>"
	case RtBinaryList:
		return "java.util.List<byte[]>"
	case RtObjectsList:
		return "java.util.List<Object[]>"
	case RtUpdatedCount:
		return "int"
	default:
		panic(fmt.Sprintf("Unknown result type: %s", type_))
	}
}

func formatParameterType(type_ DbParameterType, enumType string) string {
	switch type_ {
	case PtString:
		if enumType != "" {
			return formatFullName(enumType)
		} else {
			return "CharSequence"
		}
	case PtInteger:
		if enumType != "" {
			return formatFullName(enumType)
		} else {
			return "Long"
		}
	case PtDecimal:
		return "java.math.BigDecimal"
	case PtDouble:
		return "Double"
	case PtDateTime:
		return "java.util.Date"
	case PtBoolean:
		return "Boolean"
	case PtBinary:
		return "java.nio.ByteBuffer"
	case PtIntegerSet:
		if enumType != "" {
			return "java.util.Collection<" + formatFullName(enumType) + ">"
		} else {
			return "java.util.Collection<Long>"
		}
	case PtStringSet:
		if enumType != "" {
			return "java.util.Collection<" + formatFullName(enumType) + ">"
		} else {
			return "java.util.Collection<? extends CharSequence>"
		}
	default:
		panic(fmt.Sprintf("Unknown parameter type: %s", type_))
	}
}

func formatIntegerLiteral(s string, lineInfo *LineInfo) (string, error) {
	intValue, err := strconv.ParseInt(strings.ToLower(s), 0, 64)
	if err != nil {
		return "", fmt.Errorf("Cannot convert %q to integer (%s)", s, lineInfo)
	}
	return fmt.Sprintf("%dL", intValue), nil
}

func formatDecimalLiteral(s string, lineInfo *LineInfo) (string, error) {
	_, err := strconv.ParseFloat(strings.ToLower(s), 64)
	if err != nil {
		return "", fmt.Errorf("Cannot convert %q to decimal (%s)", s, lineInfo)
	}
	return "new java.math.BigDecimal(\"" + s + "\")", nil
}

func formatDoubleLiteral(s string, lineInfo *LineInfo) (string, error) {
	doubleValue, err := strconv.ParseFloat(strings.ToLower(s), 64)
	if err != nil {
		return "", fmt.Errorf("Cannot convert %q to double (%s)", s, lineInfo)
	}
	return fmt.Sprintf("%fD", doubleValue), nil
}

func formatDateTimeLiteral(s string, lineInfo *LineInfo) (string, error) {
	var datetimeValue time.Time
	var err error
	datetimeValue, err = time.ParseInLocation("2006-01-02", s, LOCAL_TIME_ZONE)
	if err != nil {
		datetimeValue, err = time.ParseInLocation("2006-01-02 15:04:05", s, LOCAL_TIME_ZONE)
	}
	if err != nil {
		datetimeValue, err = time.ParseInLocation("2006-01-02T15:04:05", s, LOCAL_TIME_ZONE)
	}
	if err != nil {
		datetimeValue, err = time.ParseInLocation("2006-01-02T15:04:05-0700", s, LOCAL_TIME_ZONE)
	}
	if err != nil {
		return "", fmt.Errorf("Cannot convert %q to date time (%s)", s, lineInfo)
	}
	return fmt.Sprintf("new java.util.Date(%dL) /* %s */", datetimeValue.Unix()*1000, datetimeValue.Format(time.UnixDate)), nil
}

func formatBooleanLiteral(s string, lineInfo *LineInfo) (string, error) {
	s = strings.ToLower(s)
	if s == "true" || s == "yes" || s == "on" || s == "1" || s == "t" || s == "y" {
		return "Boolean.TRUE", nil
	} else if s == "false" || s == "no" || s == "off" || s == "0" || s == "f" || s == "n" {
		return "Boolean.FALSE", nil
	} else {
		return "", fmt.Errorf("Cannot convert %q to boolean (%s)", s, lineInfo)
	}
}

func formatBinaryLiteral(s string, lineInfo *LineInfo) (string, error) {
	// 使用base64编码进行解码，然后生成对应的字节数组字面量。
	byteArray, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return "", fmt.Errorf("Cannot convert %q to base64 bytes (%s)", s, lineInfo)
	}
	byteStrArray := make([]string, len(byteArray))
	for index, byteValue := range byteArray {
		byteStrArray[index] = formatByteLiteral(byteValue)
	}
	return "new byte[]{" + strings.Join(byteStrArray, ", ") + "}", nil
}

func formatEnumLiteral(s, enumType string, lineInfo *LineInfo) (string, error) {
	// 枚举类型，需要找到该枚举类型作为前缀。
	if s == "" {
		return "null", nil
	}
	return formatFullName(enumType) + "." + IdentToUpper(s), nil
}

func formatIntegerSetLiteral(s string, lineInfo *LineInfo) (string, error) {
	result := make([]string, 0, 16)
	for _, rs := range strings.Split(s, ",") {
		rs = strings.TrimSpace(rs)
		if rs == "" {
			continue
		}
		ls, err := formatIntegerLiteral(rs, lineInfo)

		if err != nil {
			return "", err
		}

		result = append(result, ls)
	}

	if len(result) > 0 {
		return "java.util.Arrays.asList(" + strings.Join(result, ", ") + ")", nil
	} else {
		return "java.util.Collections.emptyList()", nil
	}
}

func formatStringSetLiteral(s, enumType string, lineInfo *LineInfo) (string, error) {
	result := make([]string, 0, 16)
	for _, rs := range strings.Split(s, ",") {
		var ls string
		var err error
		if enumType != "" {
			rs = strings.TrimSpace(rs)
			if rs == "" {
				continue
			}
			ls, err = formatEnumLiteral(rs, enumType, lineInfo)
		} else {
			ls, err = formatStringLiteral(rs), nil
		}

		if err != nil {
			return "", err
		}

		result = append(result, ls)
	}

	if len(result) > 0 {
		return "java.util.Arrays.asList(" + strings.Join(result, ", ") + ")", nil
	} else {
		return "java.util.Collections.emptyList()", nil
	}
}

// formatStringLiteral 对字符串进行转义，用于生成java字符串。
// s 待转义的字符串。
// 返回转义结果，自动在两端加双引号，并且对特殊字符进行转义。
func formatStringLiteral(s string) string {
	if s == "" {
		return "\"\""
	} else {
		buf := make([]rune, 0, int(float32(len(s))*1.5)+2)
		buf = append(buf, '"')
		for _, r := range s {
			if r == '"' {
				buf = append(buf, '\\', '"')
			} else if r == '\\' {
				buf = append(buf, '\\', '\\')
			} else if r == '\t' {
				buf = append(buf, '\\', 't')
			} else if r == '\n' {
				buf = append(buf, '\\', 'n')
			} else if r == '\r' {
				// 忽略回车符。
			} else if r < rune(0x20) || r > rune(0x7f) {
				uni := fmt.Sprintf("%04x", r)
				buf = append(buf, '\\', 'u')
				buf = append(buf, []rune(uni)...)
			} else {
				buf = append(buf, r)
			}
		}
		buf = append(buf, '"')
		return string(buf)
	}
}

// 对字节进行转义，用于生成java的字节字面量。
func formatByteLiteral(b byte) string {
	if b < 128 {
		return fmt.Sprintf("%#02x", b)
	} else {
		return fmt.Sprintf("-%#02x", 256-int(b))
	}
}

// 将SQL命令转化为多行，并进行必要的缩进。
func formatSql(sql string, resultEntity *DbEntity) []string {
	result := []string{}

	if sql == "" {
		return result
	}

	lines := strings.Split(sql, "\n")
	for i, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			// 跳过空行。
			continue
		}

		if i > 0 {
			// 首行之外的其它行需要缩进。
			line = "  " + line
		}

		result = append(result, line)
	}

	return result
}

// formatDefaultValue 对字段的默认值进行格式化。
// defaultValue 字符串形式的默认值。
// type_ 字段的类型。
// enumType 完整的枚举类型名。当字段类型是 FtEnum 时有效。
// lineInfo 字段的源文件位置。
// 返回可以写入到java源文件中的字面量。
func formatFieldDefaultValueLiteral(defaultValue string, type_ DbFieldType, enumType string, lineInfo *LineInfo) (string, error) {
	if type_ == FtString {
		return formatStringLiteral(defaultValue), nil
	}

	defaultValue = strings.TrimSpace(defaultValue)
	if defaultValue == "" {
		return "null", nil
	} else {
		switch type_ {
		case FtString:
			return formatStringLiteral(defaultValue), nil
		case FtInteger:
			return formatIntegerLiteral(defaultValue, lineInfo)
		case FtDecimal:
			return formatDecimalLiteral(defaultValue, lineInfo)
		case FtDouble:
			return formatDoubleLiteral(defaultValue, lineInfo)
		case FtDateTime:
			return formatDateTimeLiteral(defaultValue, lineInfo)
		case FtBoolean:
			return formatBooleanLiteral(defaultValue, lineInfo)
		case FtBinary:
			return formatBinaryLiteral(defaultValue, lineInfo)
		case FtEnum:
			return formatEnumLiteral(defaultValue, enumType, lineInfo)
		default:
			panic(fmt.Sprintf("Unknown field type: %s", type_))
		}
	}
}

// formatParameterValueLiteral 对参数值进行格式化。
// rawValue 字符串形式的参数值。
// type_ 参数的类型。
// enumType 完整的枚举类型名。当参数类型是 PtEnum 时有效。
// 返回可以写入到java源文件中的字面量。
func formatParameterValueLiteral(rawValue string, type_ DbParameterType, enumType string, lineInfo *LineInfo) (string, error) {
	if type_ != PtString || enumType != "" {
		rawValue = strings.TrimSpace(rawValue)
	}

	switch type_ {
	case PtString:
		if enumType != "" {
			return formatEnumLiteral(rawValue, enumType, lineInfo)
		} else {
			return formatStringLiteral(rawValue), nil
		}
	case PtInteger:
		if enumType != "" {
			return formatEnumLiteral(rawValue, enumType, lineInfo)
		} else {
			return formatIntegerLiteral(rawValue, lineInfo)
		}
	case PtDecimal:
		return formatDecimalLiteral(rawValue, lineInfo)
	case PtDouble:
		return formatDoubleLiteral(rawValue, lineInfo)
	case PtDateTime:
		return formatDateTimeLiteral(rawValue, lineInfo)
	case PtBoolean:
		return formatBooleanLiteral(rawValue, lineInfo)
	case PtBinary:
		return formatBinaryLiteral(rawValue, lineInfo)
	case PtIntegerSet, PtStringSet:
		panic(fmt.Sprintf("Illegal parameter type: %s", type_))
	default:
		panic(fmt.Sprintf("Unknown parameter type: %s", type_))
	}
}

// formatParameterArrayValueLiteral 对一批参数值进行格式化。
// rawValue 字符串形式的参数值，可以表示多个值，以逗号分隔。
// type_ 参数的类型。
// enumType 完整的枚举类型名。当参数类型是 PtEnum 时有效。
// 返回可以写入到java源文件中的字面量。
func formatParameterArrayValueLiteral(rawValue string, type_ DbParameterType, enumType string, lineInfo *LineInfo) (string, error) {
	switch type_ {
	case PtInteger:
		return formatIntegerSetLiteral(rawValue, lineInfo)
	case PtString:
		return formatStringSetLiteral(rawValue, enumType, lineInfo)
	case PtDecimal, PtDouble, PtDateTime, PtBoolean, PtBinary, PtStringSet, PtIntegerSet:
		panic(fmt.Sprintf("Illegal parameter type: %s", type_))
	default:
		panic(fmt.Sprintf("Unkown parameter type: %s", type_))
	}
}

func indent() {
	currentIdentSize++
}

func unIndent() {
	currentIdentSize--
	if currentIdentSize < 0 {
		panic("Variable \"currentIdentSize\" must not be negative")
	}
}

func resetIndent() {
	currentIdentSize = 0
}

func writeCodeLine(f *os.File, format string, a ...interface{}) {
	if currentIdentSize > 0 {
		fmt.Fprint(f, strings.Repeat("    ", currentIdentSize))
	}

	fmt.Fprintf(f, format, a...)
	fmt.Fprintln(f)
}

func writeBlankLine(f *os.File) {
	fmt.Fprintln(f)
}

func writeFileBriefDoc(f *os.File) {
	writeCodeLine(f, "/******************************************************************")
	writeCodeLine(f, " * @Created-by kris2 %s", APP_VERSION)
	writeCodeLine(f, " * @Date %s", time.Now().Format(time.UnixDate))
	writeCodeLine(f, " *")
	writeCodeLine(f, " * WARNING! all changes made in this file will be lost!")
	writeCodeLine(f, " *****************************************************************/")
	writeBlankLine(f)
}
